// Copyright 2008-2009 Louis DeJardin - http://whereslou.com
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// 
using System;
using System.Linq;
using System.Collections.Generic;
using Spark;
using Spark.Compiler;
using NRails.Spark.ChunkVisitors;
using Spark.Parser.Code;

namespace NRails.Spark.ChunkVisitors
{
    
    #pragma warning disable 10005 
    
    public class GlobalMembersVisitor : Spark.Compiler.ChunkVisitors.ChunkVisitor
    {
        private _source : SourceWriter;
        private _globalSymbols : Dictionary[string, object];
        private _nullBehaviour : NullBehaviour;
        _viewDataAdded : Dictionary[string, string] = Dictionary.[string, string]();
        _globalAdded : Dictionary[string, GlobalVariableChunk] = Dictionary.[string, GlobalVariableChunk]();

        public this(output : SourceWriter, globalSymbols : Dictionary[string, object], nullBehaviour : NullBehaviour)
        {
            _source = output;
            _globalSymbols = globalSymbols;
            _nullBehaviour = nullBehaviour;
        }
        
        private CodeIndent(chunk : Chunk) : SourceWriter
        {
            if (_source.AdjustDebugSymbols)
            {
                if (chunk != null && chunk.Position != null)
                {
                    _source.StartOfLine = false;
                    _source
                        .WriteDirective("#line {0} \"{1}\"", chunk.Position.Line, chunk.Position.SourceContext.FileName)
                        .Indent(chunk.Position.Column - 1);
                }
                else
                {
                    _source.StartOfLine = false;
                    _source.WriteLine("#line default");
                }
            }
            else
                _source;
        }

        private CodeHidden() : void
        {
            /*if (_source.AdjustDebugSymbols)
                _source.WriteDirective("#line hidden");*/
        }

        private CodeDefault() : void 
        {
            when (_source.AdjustDebugSymbols)
                _source.WriteDirective("#line default");
        }

        protected override Visit(chunk : GlobalVariableChunk ) : void
        {
            when (!_globalSymbols.ContainsKey(chunk.Name))
                _globalSymbols.Add(chunk.Name, null);

            if (_globalAdded.ContainsKey(chunk.Name))
            {
                when (_globalAdded[chunk.Name].Type != (TypeHelper.CorrectType(chunk.Type):object) ||
                     _globalAdded[chunk.Name].Value != (chunk.Value:object))
                {
                    throw CompilerException(string.Format("The global named {0} cannot be declared repeatedly with different types or values",
                                                              chunk.Name));
                }
            }
            else
            {
                def _type = TypeHelper.CorrectType(chunk.Type) ?? "object" : Snippets;
                def typeParts = _type.ToString().Split(' ', '\t');
                _ = if (typeParts.Contains("readonly"))
                {
                    _source.WriteFormat("\r\n    mutable {1} : {0} = {2};",
                                         _type, chunk.Name, chunk.Value);
                }
                else
                {
                    _source.WriteFormat(
                        "\r\n    mutable _{1} : {0} = {2};\r\n    public {1} : {0}  {{ get {{_{1};}} set {{_{1} = value;}} }}",
                        _type, chunk.Name, chunk.Value);
                }
                _ = _source.WriteLine();
            }
        }


        protected override Visit(chunk : ViewDataModelChunk) : void
        {
            if (Snippets.IsNullOrEmpty(chunk.TModelAlias))
            {}              
            else
            {    
            _source
                .WriteCode(chunk.TModelAlias)
                .Write(" : ")
                .WriteCode(chunk.TModel)
                .WriteLine();
            CodeIndent(chunk).WriteLine("{get {ViewData.Model;}}");
            CodeDefault();
            }
        }

        protected override Visit(chunk : ViewDataChunk) : void 
        {
            def key = chunk.Key;
            def name = chunk.Name;
            def _type = TypeHelper.CorrectType(chunk.Type) ?? "object" : Snippets;

            when (!_globalSymbols.ContainsKey(name))
                _globalSymbols.Add(name, null);

            if (_viewDataAdded.ContainsKey(name))
            {
                def s = String.Format("{0}:{1}",key, _type);
                when (_viewDataAdded[name] != s)
                {
                    throw  CompilerException(
                        string.Format("The view data named {0} cannot be declared with different types '{1}' and '{2}'",
                                      name, _type, _viewDataAdded[name]));
                }             
            }
            else
            {
                def s = String.Format("{0}:{1}",key, _type);
                _viewDataAdded.Add(name, s);
                _source.Write(String.Format("{0} : {1}\n", name, _type));
                if (Snippets.IsNullOrEmpty(chunk.Default))
                {
                    CodeIndent(chunk)
                    .Write("{get { ")
                    .Write("ViewData.Eval(\"")
                    .Write(key)
                    .WriteLine("\") :> ")
                    .WriteCode(_type)
                    .WriteLine(" }}");
                }
                else
                {
                CodeIndent(chunk)
                    .Write("{get { ")
                    .Write("ViewData.Eval(\"")
                    .Write(key)
                    .WriteLine("\") :> ")
                    .WriteCode(_type)
                    .WriteLine(" ?? ")
                    .WriteCode(chunk.Default)
                    .WriteLine(");}}");
                }
                CodeDefault();
            }
        }

        protected override Visit(chunk : ExtensionChunk) : void
        {
            chunk.Extension.VisitChunk(this, OutputLocation.ClassMembers, chunk.Body, _source.GetStringBuilder());
        }

        protected override Visit(chunk : MacroChunk) : void
        {
            _source.Write(string.Format("\r\n    {0}(", chunk.Name));            
            mutable delimiter = "";
            foreach (parameter in chunk.Parameters)
            {
                _source.Write(delimiter).WriteCode(parameter.Type).Write(" ").Write(parameter.Name);
                delimiter = ", ";
            }
            _source.WriteLine(") : object ");
            CodeIndent(chunk).WriteLine("{");
            CodeHidden();
            _source.WriteLine("        using(OutputScope(new System.IO.StringWriter()))");
            _source.WriteLine("        {");
            CodeDefault();

            def variables = Dictionary.[string, object]();
            foreach (param in chunk.Parameters)
            {
                variables.Add(param.Name, null);
            }
            def generator = GeneratedCodeVisitor(_source, variables, _nullBehaviour);
            generator.Accept(chunk.Body);

            CodeHidden();
            _source.WriteLine("            HTML(Output);");
            _source.WriteLine("        }");
            _source.WriteLine("    }");
            CodeDefault();
        }
    }
    #pragma warning restore 10005 
}
